const utils = {
    // 获取非行间样式
    /**
     * @param   obj     <DOMObject>     要获取样式的元素对象
     * @param   attr    <string>        属性名
     * @return          <string>        获取到的样式的值
     */
    getStyle: function (obj, attr) {    // obj是对象，attr是值
        if (obj.currentStyle) {   // 针对ie获取非行间样式
            return obj.currentStyle[attr];
        } else {
            return getComputedStyle(obj, false)[attr];   // 针对非ie
        }
    },



    // 添加事件监听
    /**
     * @param   ele         <DOMObject>   要添加事件的DOM元素
     * @param   type        <string>      事件类型(不带on)
     * @param   fn          <function>    事件处理函数
     * @param   [isCapture] <boolean>     是否捕获, true代表捕获, false代表冒泡, 默认为false
     */
    on: function (ele, type, fn, isCapture) {
        if (isCapture === undefined) {
            isCapture = false;
        }
        if (ele.attachEvent) {
            // IE
            ele.attachEvent('on' + type, fn);
        } else {
            ele.addEventListener(type, fn, isCapture);
        }
    },



    // 移除事件监听
    /**
     * @param   ele         <DOMObject>   要添加事件的DOM元素
     * @param   type        <string>      事件类型(不带on)
     * @param   fn          <function>    事件处理函数
     * @param   [isCapture] <boolean>     是否捕获, true代表捕获, false代表冒泡, 默认为false
     */
    off: function (ele, type, fn, isCapture) {
        if (isCapture === undefined) {
            isCapture = false;
        }
        if (ele.detachEvent) {
            // IE
            ele.detachEvent('on' + type, fn);
        } else {
            ele.removeEventListener(type, fn, isCapture);
        }
    },



    // 封装匀速运动
    /**
     * @param   ele         <DOMObject>     要运动的元素对象
     * @param   attr        <string>        运动的属性名
     * @param   end         <number>        运动的终点, 单位px
     * @param   duration    <number>        运动的总时长, 单位ms
     * @param   fn          <function>      回调函数, 在运动结束后执行的函数
     */
    move: function (ele, attr, end, duration, fn) {
        // 获取运动的起点
        var start = parseInt(this.getStyle(ele, attr));
        // 获取运动的总距离
        var distance = end - start;
        // 计算总步数: 总时间/每一步的时间=总步数(取整)
        var steps = Math.floor(duration / 30);
        // 计算速度: 总距离/总步数=速度(px/步数)
        var speed = distance / steps;
        // 记录当前步数
        var n = 0;
        ele.timer = setInterval(function () {
            n++
            // 由于attr是变量, 所以使用ele.style[attr]而不是ele.style.attr
            ele.style[attr] = start + n * speed + 'px'
            // 利用总步数判断运动停止, 当 n(当前步数) === 总步数时, 停止定时器
            if (n === steps) {
                // 停止定时器
                clearInterval(ele.timer);
                // 固定在终点位置
                ele.style[attr] = end + 'px';
                // 逻辑短路, 如果fn无效, 则隐私转换为false, 不会执行fn()
                // 回调函数写法
                fn && fn();
            }
        }, 30);
    },



    // 封装缓冲运动
    /**
     * 缓冲算法速度与时间没有关系, 所以不需要传时间, 时间就是根据距离来自动决定的
     * @param   ele         <DOMObject>     要运动的元素对象
     * @param   attr        <string>        运动的属性名
     * @param   end         <number>        运动的终点, 单位px
     * @param   fn          <function>      回调函数, 在运动结束后执行的函数
     */
    move1: function (ele, attr, end, fn) {
        // 开启定时器之前把旧的先清除掉
        clearInterval(ele.timer);
        // 获取起点位置
        var start = parseInt(this.getStyle(ele, attr));
        ele.timer = setInterval(function () {
            // 计算剩余距离
            var distance = parseInt(end) - start;
            /*
                原理:
                    每次在原来的基础上加上速度, 而速度每次都减少1/10, 速度逐渐减少
                        速度 = 剩余的距离 / 10;
                        speed = distance / 10
         
                    每次更新distance, 如果end和start都不变, 那么distance也会不变
                        剩余的距离 = 终点位置 - 开始的位置
                        distance = end - start
         
                    每次更新start的位置
                        start += speed
         
                    当start>end(运动是负方向), distance/10为-0.8px, 向上取整为0px, 需要改为向下取整
                        disatance > 0 ? Math.ceil(disance / 10) : Math.floor(distance / 10)
        
            */
            // 速度(px): 当distance/10为0.8px时, 向上取整为1px, 之后走的每一步都是1px
            var speed = distance > 0 ? Math.ceil(distance / 10) : Math.floor(distance / 10);
            // 更新start
            start += speed;
            // 更改ele属性
            ele.style[attr] = start + 'px';
            // 最后走的都是1px, 所以能刚好到达终点
            if (start === end) {
                clearInterval(ele.timer);
                fn && fn();
            }
        }, 30);
    },



    // 取cookie
    /**
     * @param   key     <string>    要取的cookie的值
     * @return          <string>    这条cookie的值(如果这条cookie不存在, 则返回undefined)
     */
    getCookie: function (key) {
        // 取到所有的cookie(NAME=VALUE; Expires=DATE; Path=PATH; Domain=DOMAIN_NAME; SECURE)
        var str = document.cookie;
        // 先按照'; '来切开每一条cookie
        var arr = str.split('; ');
        // arr的每一个item就是一条cookie, 再把item按照'='来切割, 把属性名和属性值分开
        var obj = {};
        arr.forEach(item => {
            // subArr是切开之后的数组, 这个数组的第0个元素是属性名, 第一个元素是属性值
            var subArr = item.split('=');
            // 属性名subArr[0] 这个整体作为obj的属性名, 这里不能用点. 只能用中括号[]
            // 属性值subArr[1] 是编码之后的属性值, 解码之后赋值
            obj[subArr[0]] = decodeURIComponent(subArr[1]);
        });
        return obj[key];
    },



    // 存cookie
    /**
     * @param   key     <string>    cookie的名称
     * @param   value   <string>    cookie的值
     * @param   options <object>    path和expires参数, 例如: { expires: 7, path: '/' } 指的是7天过期存根目录
     */
    setCookie: function (key, value, options) {
        // 给value编码再存
        var str = `${key}=${encodeURIComponent(value)}`;
        if (options) {
            // 先判断options是否传递了
            if (options.expires) {
                // 设置过期时间options.expires
                var date = new Date();
                date.setDate(date.getDate() + options.expires);
                str += `;expires=${date.toUTCString()}`;
            }
            if (options.path) {
                str += `;path=${options.path}`;
            }
        }
        // 把拼接好的字符串存到cookie
        document.cookie = str;
    },



    // 封装ajax的get请求
    /**
     * @param   url     <string>    请求的路径
     * @param   query   <object>    请求要携带的参数
     * @param   fn      <function>  请求成功以后的回调函数
     * @param   [isJson]  <boolean>   请求返回的数据是否是json格式, 默认为true
     */
    get: function (url, query, fn, isJson = true) {// isJson = true ES6新语法, 参数默认值, 如果参数不传, 参数默认值就是true
        // 如果有参数, 先在url后面把参数拼接上
        if (query) {
            url += '?';
            // 遍历query, 把每一个属性都拼接在url后面
            for (var key in query) {
                url += `${key}=${query[key]}&`;
            }
            // 拼接完成以后多出一个&
            url = url.slice(0, -1); // 从0开始截取到倒数第一个结束, 只保留了除最后一个&以外的字符串
        }
        // 1. 创建ajax核心对象
        var xhr = new XMLHttpRequest();
        // 2. 通过open()打开跟后端的链接
        /*
            open有三个参数
                1. 请求的method(get/post)
                2. 请求的url
                3. 是否异步(Boolean), 默认为true, 一般不用传
        */
        xhr.open('get', url);
        // 3. 通过send来发送请求
        // 作为get请求send不需要任何参数
        xhr.send();
        // 4. 监听状态码的改变
        /*
            状态值readyState: 0~4五个值, 代表请求的5个阶段, 4代表完成
            状态码status: 200代表成功
        */
        xhr.onreadystatechange = function () {
            // 判断状态码readyState和状态值status是否正确
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    // 请求成功
                    // 根据isJson来决定是否转换
                    var date = isJson ? JSON.parse(xhr.responseText) : xhr.responseText;
                    fn && fn(date);
                } else {
                    alert('请求失败, 状态码为: ' + xhr.status);
                }
            }
        }
    },



    // 封装ajax的post请求
    /**
     * @param   url         <string>    请求的路径, 例如: ./ajax.php?id=1
     * @param   query       <object>    请求要携带的参数, 传到地址栏上, 例如: ./ajax.php?id=1&name=lisi&age=13
     * @param   fn          <function>  请求成功以后的回调函数
     * @param   [isJson]    <boolean>   请求返回的数据是否是json格式, 默认为true
     */
    post: function (url, query, fn, isJson = true) {
        var str = '';
        if (query) {
            for (var key in query) {
                str += `${key}=${query[key]}&`;
            }
            str = str.slice(0, -1);
        }
        var xhr = new XMLHttpRequest();
        xhr.open('post', url);
        // 设置请求头的contentType为urlencoded方式, send发送过去的参数后端就可以通过这
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        // post请求参数在请求体里, 所以要在send里传参
        xhr.send(str);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    var date = isJson ? JSON.parse(xhr.responseText) : xhr.responseText;
                    fn && fn(date);
                }
            }
        }
    },



    // 发送jsonp请求(跨域请求)
    /**
     * 
     * @param   url      <string>    接口的路径
     * @param   cbName   <string>    回调函数名
     * @param   [query]  <object>    其他的一些参数
     */
    jsonp: function (url, cbName, query) {
        // 创建script标签
        url += `?cb=${cbName}`;
        if (query) {
            for (var key in query) {
                url += `&${key}=${query[key]}`;
            }
        }
        var script = document.createElement('script');
        script.src = url;
        document.body.appendChild(script);
        // 一旦script存在, 请求就发出去了, script就没用了, 可以直接删除
        document.body.removeChild(script);  // 过河拆桥
    },



    // 基于Promise的ajax的get请求
    /**
     * @param   url     <string>    请求的路径
     * @param   query   <object>    请求要携带的参数
     * @param   [isJson]  <boolean>   请求返回的数据是否是json格式, 默认为true
     */
    fetch: function (url, query, isJson = true) {
        if (query) {
            url += '?';
            // 遍历query, 把每一个属性都拼接在url后面
            for (var key in query) {
                url += `${key}=${query[key]}&`;
            }
            // 拼接完成以后多出一个&
            url = url.slice(0, -1); // 从0开始截取到倒数第一个结束, 只保留了除最后一个&以外的字符串
        }
        // 在这里需要把promise return出去, 这样将来调用这个方法才能.then()
        return new Promise((resolve, reject) => {
            var xhr = new XMLHttpRequest();
            xhr.open('get', url);// 第三个参数, 默认为true, 异步
            xhr.send();
            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        resolve(isJson ? JSON.parse(xhr.responseText) : xhr.responseText);
                    } else {
                        reject();
                    }
                }
            }
        });
    },



    // 基于Promise的ajax的post请求
    /**
     * @param   url         <string>    请求的路径, 例如: ./ajax.php?id=1
     * @param   query       <object>    请求要携带的参数, 传到地址栏上, 例如: ./ajax.php?id=1&name=lisi&age=13
     * @param   [isJson]    <boolean>   请求返回的数据是否是json格式, 默认为true
     */
    fetchPost: function (url, query, isJson = true) {
        var str = '';
        if (query) {
            for (var key in query) {
                str += `${key}=${query[key]}&`;
            }
            str = str.slice(0, -1);
        }
        return new Promise((resolve, reject) => {
            var xhr = new XMLHttpRequest();
            xhr.open('post', url);
            // 设置请求头的contentType为urlencoded方式, send发送过去的参数后端就可以通过这
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            // post请求参数在请求体里, 所以要在send里传参
            xhr.send(str);
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        resolve(isJson ? JSON.parse(xhr.responseText) : xhr.responseText);
                    } else {
                        reject();
                    }
                }
            }
        });
    },
}
